package com.ddw.meedeeuh.shared.database;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

/**
 * Represents a helper class for generating query parameter lists
 * and keeping a reference to the indexes of keys in the list.
 * This helps with locating the field ordinals when a sql query
 * returns a rows/columns response without the overhead of having
 * to lookup within the row resource
 * @author cmcnicholas
 *
 */
public class QueryParamHelper {

	private final Map<String, QueryParam> params = new HashMap<String, QueryParam>();
	private int nextIndex = 0;

	/**
	 * Adds a parameter to the internal map, if the key
	 * is a duplicate it is not added
	 * @param key the key to add
	 * @return the index the key has been set as
	 */
	public int addParam(Object key) {
		return addParam(key.toString());
	}

	/**
	 * Adds a parameter to the internal map, if the key
	 * is a duplicate it is not added
	 * @param key the key to add
	 * @return the index the key has been set as
	 */
	public int addParam(String key) {
		return addParam(key, null);
	}

	/**
	 * Adds a parameter to the internal map, if the key
	 * is a duplicate it is not added
	 * @param key the key to add
	 * @param tableAlias the table alias to prepend to the key when making select statements
	 * @return the index the key has been set as
	 */
	public int addParam(Object key, String tableAlias) {
		return addParam(key.toString(), tableAlias);
	}

	/**
	 * Adds a parameter to the internal map, if the key
	 * is a duplicate it is not added
	 * @param key the key to add
	 * @param tableAlias the table alias to prepend to the key when making select statements
	 * @return the index the key has been set as
	 */
	public int addParam(String key, String tableAlias) {
		if(key == null) {
			throw new IllegalArgumentException("The key parameter cannot be null");
		}

		if(getParams().containsKey(key) == false) {
			getParams().put(key, new QueryParam(nextIndex, key, tableAlias));
			return nextIndex++;
		} else {
			return getParams().get(key).getIndex();
		}
	}

	/**
	 * Gets the index of a key
	 * @param key the key to find the index of
	 * @return an index
	 */
	public int index(Object key) {
		return index(key.toString());
	}

	/**
	 * Gets the index of a key
	 * @param key the key to find the index of
	 * @return an index
	 */
	public int index(String key) {
		if(key == null) {
			throw new IllegalArgumentException("The key parameter cannot be null");
		}

		// Get the parameter and check it exists
		final QueryParam param = getParams().get(key);
		if(param == null) {
			throw new IndexOutOfBoundsException("The key '" + key + "' does not exist");
		}

		return param.getIndex();
	}

	/**
	 * Gets the bind index of a key, the bind index starts at one and is
	 * not zero based
	 * @param key the key to find the index of
	 * @return an index
	 */
	public int bindIndex(Object key) {
		return bindIndex(key.toString());
	}

	/**
	 * Gets the bind index of a key, the bind index starts at one and is
	 * not zero based
	 * @param key the key to find the index of
	 * @return an index
	 */
	public int bindIndex(String key) {
		return index(key) + 1;
	}

	/**
	 * Generates a select parameter query in the form
	 * <pre>a.key1, a.key2, b.key3, c.key4</pre>
	 * Where 'a', 'b' and 'c' are table alias'
	 * @return a select field name query
	 */
	public String getSelectQuery() {
		// Sort the parameters
		final List<Entry<String, QueryParam>> params = new ArrayList<Entry<String, QueryParam>>(getParams().entrySet());
		Collections.sort(params, new Comparator<Entry<String, QueryParam>>() {

			@Override
			public int compare(Entry<String, QueryParam> p1, Entry<String, QueryParam> p2) {
				return p1.getValue().getIndex() - p2.getValue().getIndex();
			}

		});

		// Build the parameter sql string
		final StringBuilder builder = new StringBuilder();
		for(int i = 0, s = params.size(); i < s; i++) {
			if(i > 0) {
				builder.append(", ");
			}
			builder.append(params.get(i).getValue().toString());
		}
		return builder.toString();
	}

	/**
	 * Gets the total number of parameters
	 * @return
	 */
	public int getParameterCount() {
		return getParams().size();
	}

	/**
	 * Gets the parameters map
	 * @return a map of parameters
	 */
	private Map<String, QueryParam> getParams() {
		return this.params;
	}

	/**
	 * Represents a query parameter which holds the parameters
	 * index, key and table alias information
	 * @author cmcnicholas
	 *
	 */
	private static class QueryParam {

		private final int index;
		private final String key;
		private final String tableAlias;

		/**
		 * Creates a new query param instance
		 * @param index the index of the parameter
		 * @param key the key of the parameter
		 * @param tableAlias the optional table alias
		 */
		public QueryParam(int index, String key, String tableAlias) {
			if(key == null) {
				throw new IllegalArgumentException("The key parameter cannot be null");
			}

			this.index = index;
			this.key = key;
			this.tableAlias = tableAlias;
		}

		@Override
		public String toString() {
			if(getTableAlias() != null) {
				return getTableAlias() + "." + getKey();
			} else {
				return getKey();
			}
		}

		/**
		 * Gets the parameters index
		 * @return a zero based index
		 */
		public int getIndex() {
			return this.index;
		}

		/**
		 * Gets the parameters key name
		 * @return a name
		 */
		public String getKey() {
			return this.key;
		}

		/**
		 * Gets the optional parameters table alias
		 * @return a table alias or null
		 */
		public String getTableAlias() {
			return this.tableAlias;
		}

	}

}
